package vd

import (
	"io"
	"fmt"
	"bufio"
	"errors"
	"strings"
	"strconv"
	"math/big"
	"io/ioutil"
	"encoding/base64"
	"kumachan/standalone/util"
)


type deserializeContext struct {
	*Deserializer
	Depth       uint
	ReturnKey   *string
	ReturnType  **Type
}
type deserializeReader struct {
	*bufio.Reader
	unreadIndention  uint
	linesRead        uint
}

func Deserialize(input io.Reader, deserializer *Deserializer) (Object, *Type, error) {
	var t *Type
	var ctx = deserializeContext {
		Deserializer: deserializer,
		Depth:        0,
		ReturnKey:    nil,
		ReturnType:   &t,
	}
	var buffered_input = bufio.NewReader(input)
	line, _, err := util.WellBehavedFscanln(buffered_input)
	if err != nil { return nil, nil, err }
	if line != header { return nil, nil, errors.New("invalid header") }
	var reader = &deserializeReader {
		Reader: buffered_input,
		unreadIndention: 0,
		linesRead: 1,
	}
	obj, err := deserialize(reader, ctx)
	var n = (reader.linesRead + 1)
	if err != nil { return nil, nil,
		fmt.Errorf("error near line %d: %w", n, err) }
	if t == nil { panic("something went wrong") }
	return ctx.ReflectOutward(obj), t, nil
}

func deserialize(input *deserializeReader, ctx deserializeContext) (Object, error) {
	line, _, err := readLine(input)
	if err != nil { return nil, err }
	type_str, key, err := (func() (string, string, error) {
		if ctx.ReturnKey != nil {
			var key_part, type_part = stringSplitFirstSegment(line)
			if key_part == "" { return "", "", errors.New("missing field name") }
			return type_part, key_part, nil
		} else {
			return line, "", nil
		}
	})()
	if err != nil { return nil, err }
	var parsed_type, ok = TypeParse(type_str)
	if !(ok) { return nil, errors.New(fmt.Sprintf("invalid type: %s", type_str)) }
	var t = parsed_type
	if ctx.ReturnKey != nil {
		*ctx.ReturnKey = key
	}
	if ctx.ReturnType != nil {
		*ctx.ReturnType = t
	}
	switch t.kind {
	case Unit:
		return ctx.Unit, nil
	case Bool:
		return readPrimitive(input, ctx.Depth, func(str string) (Object, error) {
			switch str {
			case "true":  return ctx.ReadBool(true), nil
			case "false": return ctx.ReadBool(false), nil
			default:      return nil, errors.New("invalid bool")
			}
		})
	case Float:
		return readPrimitive(input, ctx.Depth, func(str string) (Object, error) {
			value, err := strconv.ParseFloat(str, 64)
			if err != nil {
				return nil, fmt.Errorf("invalid float: %w", err)
			}
			if !(util.IsNormalFloat(value)) {
				return nil, errors.New("invalid float")
			}
			return ctx.ReadFloat(value), nil
		})
	case Complex:
		return readPrimitive(input, ctx.Depth, func(str string) (Object, error) {
			value, err := strconv.ParseComplex(str, 128)
			if err != nil {
				return nil, fmt.Errorf("invalid complex: %w", err)
			}
			if !(util.IsNormalComplex(value)) {
				return nil, errors.New("invalid complex")
			}
			return ctx.ReadComplex(value), nil
		})
	case Integer:
		return readPrimitive(input, ctx.Depth, func(str string) (Object, error) {
			var value big.Int
			var _, ok = value.SetString(str, 10)
			if !(ok) { return nil, errors.New("invalid int") }
			var obj, fit = ctx.ReadInteger(&value)
			if fit {
				return obj, nil
			} else {
				return nil, errors.New("integer too big")
			}
		})
	case String:
		return readPrimitive(input, ctx.Depth, func(str string) (Object, error) {
			value, err := strconv.Unquote(str)
			if err != nil { return nil, fmt.Errorf("invalid string: %w", err) }
			return ctx.ReadString(value), nil
		})
	case Binary:
		return readPrimitive(input, ctx.Depth, func(str string) (Object, error) {
			var str_reader = strings.NewReader(str)
			var decoder = base64.NewDecoder(base64.StdEncoding, str_reader)
			value, err := ioutil.ReadAll(decoder)
			if err != nil { return nil, fmt.Errorf("invalid binary: %w", err) }
			return ctx.ReadBinary(value), nil
		})
	case Array:
		var array, err = ctx.CreateArray(t)
		if err != nil { return nil, err }
		var i = 0
		for {
			n, err := readIndent(input)
			if err != nil { return nil, err }
			if n == (ctx.Depth + 1) {
				var item_ctx = deserializeContext {
					Deserializer: ctx.Deserializer,
					Depth:        (ctx.Depth + 1),
				}
				item, err := deserialize(input, item_ctx)
				if err != nil { return nil, err }
				err = ctx.AppendItem(&array, item)
				if err != nil { return nil, err }
				i += 1
			} else if n <= ctx.Depth {
				unreadIndent(input, n)
				return array, nil
			} else {
				return nil, errors.New("wrong indention")
			}
		}
	case Optional:
		n, err := readIndent(input)
		if err != nil { return nil, err }
		if n == (ctx.Depth + 1) {
			var inner_ctx = deserializeContext {
				Deserializer: ctx.Deserializer,
				Depth:        (ctx.Depth + 1),
			}
			inner, err := deserialize(input, inner_ctx)
			if err != nil { return nil, err }
			return ctx.MaybeSome(inner, t)
		} else if n <= ctx.Depth {
			unreadIndent(input, n)
			return ctx.MaybeNone(t)
		} else {
			return nil, errors.New("wrong indention")
		}
	case Record:
		var entries = make(map[string] Object)
		var types = make(map[string] *Type)
		for {
			n, err := readIndent(input)
			if err != nil { return nil, err }
			if n == (ctx.Depth + 1) {
				var key string
				var value_t *Type
				var entry_ctx = deserializeContext {
					Deserializer: ctx.Deserializer,
					Depth:        (ctx.Depth + 1),
					ReturnKey:    &key,
					ReturnType:   &value_t,
				}
				value, err := deserialize(input, entry_ctx)
				if err != nil { return nil, err }
				var _, exists = entries[key]
				if exists { return nil, errors.New(fmt.Sprintf(
					"duplicate field %s", key))}
				entries[key] = value
				types[key] = value_t
			} else if n <= ctx.Depth {
				unreadIndent(input, n)
				var tid = t.identifier
				draft, fields, err := ctx.CreateRecord(tid, uint(len(entries)))
				if err != nil { return nil, err }
				for key, value := range entries {
					var value_t = types[key]
					field, exists := fields(key)
					if !(exists) { return nil, errors.New(fmt.Sprintf(
						"field %s does not exist on type %s", key, tid)) }
					var field_t = field.Type
					var field_index = field.Index
					adapted, err := ctx.AssignObject(value, value_t, field_t)
					if err != nil { return nil, err }
					ctx.FillField(draft, field_index, adapted)
				}
				record, err := ctx.FinishRecord(draft, tid)
				if err != nil { return nil, err }
				return record, nil
			} else {
				return nil, errors.New("wrong indention")
			}
		}
	case Enum:
		n, err := readIndent(input)
		if err != nil { return nil, err }
		if n == (ctx.Depth + 1) {
			var case_t *Type
			var case_ctx = deserializeContext {
				Deserializer: ctx.Deserializer,
				Depth:        (ctx.Depth + 1),
				ReturnType:   &case_t,
			}
			case_value, err := deserialize(input, case_ctx)
			if err != nil { return nil, err }
			var enum_tid = t.identifier
			var case_tid = case_t.identifier
			enum_value, err := ctx.Case2Enum(case_value, enum_tid, case_tid)
			if err != nil { return nil, err }
			return enum_value, nil
		} else {
			return nil, errors.New("wrong indention")
		}
	default:
		panic("impossible branch")
	}
}

func readIndent(input *deserializeReader) (uint, error) {
	var n = uint(0) + input.unreadIndention
	input.unreadIndention = 0
	for {
		char, err := input.ReadByte()
		if err == io.EOF { return 0, nil }
		if err != nil { return 0, err }
		if char == indent {
			n += 1
		} else {
			err := input.UnreadByte()
			if err != nil { panic("something went wrong") }
			return n, nil
		}
	}
}
func unreadIndent(input *deserializeReader, n uint) {
	input.unreadIndention += n
}
func readPrimitive(input *deserializeReader, depth uint, f func(string)(Object,error)) (Object,error) {
	n, err := readIndent(input)
	if err != nil { return nil, err }
	if n != (depth + 1) { return nil, errors.New("wrong indention") }
	line, _, err := readLine(input)
	if err != nil { return nil, err }
	return f(line)
}
func readLine(input *deserializeReader) (string, int, error) {
	line, n, err := util.WellBehavedFscanln(input)
	if err == nil {
		input.linesRead += 1
	}
	return line, n, err
}


